/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including but not limited to the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to provide persons with access to the Software
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGE OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import {
  BreakpointRange,
  Breakpoint,
  BreakpointCallback,
  BreakpointValue,
  BreakpointMode,
  AspectRatioThreshold,
} from '../../types/breakpoint';

/**
 * @description H5断点能力类封装
 * @param customBreakpoints 自定义断点属性和范围
 */
export class BreakpointManager {
  private _breakpointRange: BreakpointRange<number>;
  private _widthBreakpointSet: (keyof BreakpointRange<BreakpointValue>)[] = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
  private _heightBreakpointSet: (keyof BreakpointRange<BreakpointValue>)[] = ['sm', 'md', 'lg'];
  private _currentWidthBreakpoint: Breakpoint = 'sm';
  private _currentHeightBreakpoint: Breakpoint = 'sm';
  private _newWidthBreakpoint: Breakpoint = 'sm';
  private _newHeightBreakpoint: Breakpoint = 'sm';
  private _containerWidth: number = 0;
  private _containerHeight: number = 0;
  private _widthCallbacks: Set<BreakpointCallback> = new Set();
  private _heightCallbacks: Set<BreakpointCallback> = new Set();
  private _className: string = '';
  private _resizeObserver: ResizeObserver | null = null;
  private _breakpointMode: BreakpointMode = BreakpointMode.Window;
  private _isBreakpointRangeSet: boolean = false;
  private _systemBreakpoint: any = null;
  private _element: Element | null = null;

  constructor(customBreakpoints?: BreakpointRange<number>, className?: string) {
    this._breakpointRange = customBreakpoints || this._getCustomBreakpoint() || {
      xs: 0,
      sm: 320,
      md: 600,
      lg: 840,
      xl: 1440,
    };
    this._className = className ?? '';
    this._isBreakpointRangeSet = !!(customBreakpoints || this._getCustomBreakpoint());
    this._getBreakpointMode();
    this._updateBreakpoint = this._updateBreakpoint.bind(this);
    this._updateBreakpoint();

    // 判断监听窗口变化/组件尺寸变化
    if (className) {
      this._element = document.querySelector(`.${className}`);
      if (this._element) {
        this._resizeObserver = new ResizeObserver(entries => {
          this._updateBreakpoint();
        });
        this._resizeObserver.observe(this._element);
      }
    } else {
      window.addEventListener('resize', this._updateBreakpoint);
    }
  }

  private _getCustomBreakpoint() {
    if ((window as any).adaptiveUiConfig) {
      return (window as any).adaptiveUiConfig.breakpoint ? (window as any).adaptiveUiConfig.breakpoint : null;
    } else {
      return null;
    }
  }

  private _getBreakpointMode(): void {
    if (this._className) {
      this._breakpointMode = BreakpointMode.Component;
    } else {
      this._systemBreakpoint = (window as any).systemBreakpoint;
      if (
        !this._isBreakpointRangeSet && 
        this._systemBreakpoint && 
        (this._systemBreakpoint.getWindowWidthBreakpoint || this._systemBreakpoint.getWindowHeightBreakpoint)
      ) {
        this._breakpointMode = BreakpointMode.System;
      } else {
        this._breakpointMode = BreakpointMode.Window;
      }
    }
  }

  private _calcWidthBreakpoint(): void {
    for (const [key, value] of Object.entries(this._breakpointRange)) {
      if (this._containerWidth >= value) {
        this._newWidthBreakpoint = key as Breakpoint;
      } else {
        break;
      }
    }
  }

  private _calcHeightBreakpoint(): void {
    const aspectRatio = this._containerHeight / this._containerWidth;
    if (aspectRatio < AspectRatioThreshold.SM) {
      this._newHeightBreakpoint = 'sm';
    } else if (aspectRatio >= AspectRatioThreshold.SM && aspectRatio < AspectRatioThreshold.MD) {
      this._newHeightBreakpoint = 'md';
    } else {
      this._newHeightBreakpoint = 'lg';
    }
  }

  private _updateBreakpoint(): void {
    // 系统动态断点获取
    if (this._breakpointMode === BreakpointMode.System) {
      if (this._systemBreakpoint.getWindowWidthBreakpoint) {
        this._newWidthBreakpoint = this._widthBreakpointSet[this._systemBreakpoint.getWindowWidthBreakpoint() || 1];
      } else {
        throw new Error('Undefined interface for obtaining dynamic horizontal breakpoints.');
      }

      if (this._systemBreakpoint.getWindowHeightBreakpoint) {
        this._newHeightBreakpoint = this._heightBreakpointSet[this._systemBreakpoint.getWindowHeightBreakpoint() || 0];
      } else {
        throw new Error('Undefined interface for obtaining dynamic vertical breakpoints.');
      }
    }

    // 自定义实现获取断点，窗口变化监听
    if (this._breakpointMode === BreakpointMode.Window) {
      this._containerWidth = window.innerWidth || 1;
      this._containerHeight = window.innerHeight ?? 0;
      this._calcWidthBreakpoint();
      this._calcHeightBreakpoint();
    }

    // 自定义实现获取断点，组件尺寸变化监听
    if (this._breakpointMode === BreakpointMode.Component) {
      const componentDom = document.querySelector(`.${this._className}`) as HTMLElement;
      this._containerWidth = componentDom?.offsetWidth || 1;
      this._containerHeight = componentDom?.offsetHeight ?? 0;
      this._calcWidthBreakpoint();
      this._calcHeightBreakpoint();
    }

    // 断点变化后，执行回调集合
    if (this._newWidthBreakpoint !== this._currentWidthBreakpoint) {
      this._currentWidthBreakpoint = this._newWidthBreakpoint;
      this._notifyWidthCallbacks();
    }
    if (this._newHeightBreakpoint !== this._currentHeightBreakpoint) {
      this._currentHeightBreakpoint = this._newHeightBreakpoint;
      this._notifyHeightCallbacks();
    }
  }

  private _notifyWidthCallbacks(): void {
    this._widthCallbacks.forEach((callback) => callback(this._currentWidthBreakpoint));
  }

  private _notifyHeightCallbacks(): void {
    this._heightCallbacks.forEach((callback) => callback(this._currentHeightBreakpoint));
  }

  public subscribeToBreakpoint(widthCallback: BreakpointCallback|null = null, heightCallback: BreakpointCallback|null = null): () => void {
    if (widthCallback) {
      this._widthCallbacks.add(widthCallback);
      widthCallback(this._currentWidthBreakpoint);
    }
    if (heightCallback) {
      this._heightCallbacks.add(heightCallback);
      heightCallback(this._currentHeightBreakpoint);
    }
    return () => {
      widthCallback === null || this._widthCallbacks.delete(widthCallback);
      heightCallback === null || this._heightCallbacks.delete(heightCallback);
    }
  }

  private _fillBreakpointValue(param: BreakpointRange<BreakpointValue>) {
    const result = { ...param };
    // 查找第一个和最后一个有值的索引
    const firstValueIndex = this._widthBreakpointSet.findIndex((key: Breakpoint) => param[key] !== undefined);
    const lastValueIndex = this._widthBreakpointSet.findLastIndex((key: Breakpoint) => param[key] !== undefined);

    // 向左填充
    for (let i = firstValueIndex - 1; i >= 0; i--) {
      result[this._widthBreakpointSet[i]] = param[this._widthBreakpointSet[firstValueIndex]];
    }

    // 向右填充
    for (let i = lastValueIndex + 1; i < this._widthBreakpointSet.length; i++) {
      result[this._widthBreakpointSet[i]] = param[this._widthBreakpointSet[lastValueIndex]];
    }
    return result as Required<BreakpointRange<BreakpointValue>>;
  }

  public useBreakpointValue(values: BreakpointRange<BreakpointValue>): any {
    const filledValues = this._fillBreakpointValue(values);
    if (this._currentWidthBreakpoint && filledValues[this._currentWidthBreakpoint] !== undefined) {
      return filledValues[this._currentWidthBreakpoint];
    } else {
      return filledValues.sm;
    }
  }

  public useVerticalBreakpointValue(values: BreakpointRange<BreakpointValue>): any {
    const filledValues = this._fillBreakpointValue(values);
    if (this._currentHeightBreakpoint && filledValues[this._currentHeightBreakpoint] !== undefined) {
      return filledValues[this._currentHeightBreakpoint];
    } else {
      return filledValues.sm;
    }
  }

  public getCurrentBreakpoint(): Breakpoint {
    return this._currentWidthBreakpoint;
  }

  public getCurrentVerticalBreakpoint(): Breakpoint {
    return this._currentHeightBreakpoint;
  }

  public getBreakpoints(): BreakpointRange<number> {
    return { ...this._breakpointRange };
  }

  public getBreakpointMode(): BreakpointMode {
    return this._breakpointMode;
  }

  public destroy(): void {
    if (!this._className) {
      window.removeEventListener('resize', this._updateBreakpoint);
    }
    if (this._resizeObserver && this._element) {
      this._resizeObserver.unobserve(this._element);
      this._resizeObserver = null;
    }
    this._widthCallbacks.clear();
    this._heightCallbacks.clear();
  }
}